using System;
using System.Text;
using System.Data;
using System.Xml;
using System.Xml.Linq;
using System.Net;
using System.IO;

namespace DataSyncSample
{
    class Program
    {

        static void Main(string[] args)
        {
            Program p = new Program();
            try
            {

                // This information will be delivered to you. You will also need to get your IP address for IP address authentication. Deliver the IP address that this
                // service will run on and we will add it to the account settings. You can use Http://spatialstream.com/samples/ShowClientIP.ashx to determine the IP 
                // to send.
                string accountName = "trialaccount"; // User account
                string userName = "user1"; // use user name
                string baseReferrer = userName; // for server to server authentication we just use the userName as the base referrer
                string baseUrl = "http://dc1.spatialstream.com/";


                // Step #1 Authenticate using trusted IP authentication and return a cookie that contains a authentication 
                // token for subsequent requests. 
                CookieContainer authCookie = p.Authenticate(userName, accountName, "Group1", baseUrl, baseReferrer);

                // Call the data sync process
                p.DataSyncRecs(baseUrl, "MY_FOLDER/DATASYNCSAMPLE", "ID", authCookie, baseReferrer);
            }
            catch (ApplicationException ex)
            {
                Console.WriteLine("Error Occured! Stack Trace = " + ex.StackTrace.ToString());
            }
            catch (Exception ex)
            {
                Console.WriteLine("Critical Error Occured! Stack Trace = " + ex.StackTrace.ToString());
            }
        }

        // Does all of the work
        public void DataSyncRecs(string baseUrl, string resourceName, string keyFieldName, CookieContainer authCookie, string baseReferrer)
        {
            // Step #2 Prepare your data for synchronize. This step is used to access the modified records and gather the data that you
            // would like to synchronize. For sample purposes we will use a hardcoded data table that contains records that we will 
            // synchronize.
            DataTable table = GetSampleDataTable();

            // Record Sequence starts at 0, Every time we create a new record the record sequence is increased by 1
            Int32 recseq = 0;

            // Step #3 Create a new version
            String versionUrl = baseUrl + "version.aspx?&action=create&recseq=" + recseq++; //recordSequence

            HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(versionUrl);
            httpWebRequest.CookieContainer = authCookie;
            httpWebRequest.Referer = "http://" + baseReferrer + "/index.html";

            WebResponse webResponse = httpWebRequest.GetResponse();
            XmlDocument versionResults = new XmlDocument();
            versionResults.Load(webResponse.GetResponseStream());
            webResponse.Close();


            String v = ""; // versionResults.Root.Attribute("versionId").Value.ToString();
            if (versionResults.SelectSingleNode("Response/@versionId") != null)
            {
                v = versionResults.SelectSingleNode("Response/@versionId").Value;
            }
            else
            {
                throw new ApplicationException("Unable to get a version id.");
            }

            try
            {
                // Create DOMS to hold information that will be passed in a post request
                XElement transactionDom = new XElement("Request");
                transactionDom.Add(new XAttribute("versionId", v));
                XElement updateRecordsDom = new XElement("Update");
                updateRecordsDom.Add(new XAttribute("resource", resourceName));
                XElement insertRecordsDom = new XElement("Insert");
                insertRecordsDom.Add(new XAttribute("resource", resourceName));

                // keeps track of the data that is currently in the queue for insert or update
                int counter = 0;

                // when counter has this many records the records are posted 
                const int chunkSize = 50;

                foreach (DataRow row in table.Rows)
                {

                    XElement rowDom = new XElement("Row");

                    // Step #4 Get Geocode
                    // This example is using GetGeocode to get the X,Y corrdinate that is used as the geometry. If your data already contains the X,Y then you can use that
                    // instead of going through the geocode process.
                    String geocodeUrl = baseUrl +
                        "GetGeocode.aspx" +
                        "?address=" + row["Street"].ToString() +
                        "&city=" + row["City"].ToString() +
                        "&state=" + row["State"].ToString() +
                        "&zip=" + row["Zip"].ToString() +
                        "&fields=*&v=latest" +
                        "&datasource=" + "Parcels,Street_Centerline";

                    httpWebRequest = (HttpWebRequest)WebRequest.Create(geocodeUrl);
                    httpWebRequest.CookieContainer = authCookie;
                    httpWebRequest.Referer = "http://" + baseReferrer + "/index.html";

                    webResponse = httpWebRequest.GetResponse();
                    XmlDocument geocodeDocumentXML = new XmlDocument();
                    geocodeDocumentXML.Load(webResponse.GetResponseStream());
                    webResponse.Close();

                    if (geocodeDocumentXML.SelectSingleNode("Response/@status").Value.ToLower() != "success" &&
                        Convert.ToInt32(geocodeDocumentXML.SelectSingleNode("Response/Results/@totalRecords").Value) > 0)
                    {
                        // ToDo: check min score.
                        // noResultsQueue.Add(geocodeUrl);
                        // handle records that do not geocode.
                    }
                    else
                    {

                        XmlNode geocodeRecord = geocodeDocumentXML.SelectSingleNode("Response/Results/Data/Row");

                        // Step #5 Call get by key to see if the record already exists within the database. If it does we will call
                        // Update to update the existing record. If it does not exist we will call insert for the existing record.
                        String getByKeyUrl = baseUrl + "GetByKey.aspx" +
                            "?keyName=" + keyFieldName +
                             "&keyValue=" + row[keyFieldName].ToString() +
                            "&datasource=" + resourceName;

                        httpWebRequest = (HttpWebRequest)WebRequest.Create(getByKeyUrl);
                        httpWebRequest.CookieContainer = authCookie;
                        httpWebRequest.Referer = "http://" + baseReferrer + "/index.html";

                        webResponse = httpWebRequest.GetResponse();
                        XmlDocument keyDocumentXML = new XmlDocument();
                        keyDocumentXML.Load(webResponse.GetResponseStream());
                        webResponse.Close();

                        string action = "";
                        if (keyDocumentXML.SelectSingleNode("Response/@status").Value.ToLower() != "success")
                        {
                            // handle error
                        }
                        else
                        {

                            int recordCount = Convert.ToInt32(keyDocumentXML.SelectSingleNode("Response/Results/@totalRecords").Value);
                            action = (recordCount == 0 ? "Insert" : "Update");

                        }

                        // Step #6 Fill the attributes on each record
                        // Add the geometry column
                        rowDom.Add(new XAttribute("GEOMETRY", geocodeRecord.Attributes["GEOMETRY"].Value));
                        foreach (DataColumn column in table.Columns)
                        {
                            // Add each column + value
                            rowDom.Add(new XAttribute(column.ColumnName, row[column.ColumnName].ToString()));
                        }

                        if (action == "Update")
                        {
                            XmlNode keyNode = keyDocumentXML.SelectSingleNode("Response/Results/Data/Row");

                            // If update we need to add a couple of session fields. 
                            rowDom.Add(new XAttribute("_SESSIONID", keyNode.Attributes["_SESSIONID"].Value));
                            rowDom.Add(new XAttribute("_RECSEQ", keyNode.Attributes["_RECSEQ"].Value));
                            rowDom.Add(new XAttribute("_CREATEDBY", keyNode.Attributes["_CREATEDBY"].Value));
                            updateRecordsDom.Add(rowDom);


                        }
                        else
                        {
                            // If insert we just need to add the _RECSEQ
                            rowDom.Add(new XAttribute("_RECSEQ", recseq++));
                            insertRecordsDom.Add(rowDom);


                        }
                        // increase the counter by one 
                        counter++;

                        if (counter >= chunkSize)
                        {
                            // commit chunk
                            postTransactionChunk(baseUrl, authCookie, baseReferrer, transactionDom);

                            // clean up xml doms that were just loaded getting ready for the next batch
                            transactionDom = new XElement("Request");
                            transactionDom.Add(new XAttribute("versionId", v));
                            updateRecordsDom = new XElement("Update");
                            updateRecordsDom.Add(new XAttribute("resource", resourceName));
                            insertRecordsDom = new XElement("Insert");
                            insertRecordsDom.Add(new XAttribute("resource", resourceName));
                            transactionDom.Add(updateRecordsDom);
                            transactionDom.Add(insertRecordsDom);

                            // reset counter
                            counter = 0;
                        }

                    }

                } // for each

                // publish any records still in the queue
                if (counter != chunkSize)
                {
                    // commit chunk
                    postTransactionChunk(baseUrl, authCookie, baseReferrer, transactionDom);

                    // clean up xml DOMS that were just loaded getting ready for the next batch
                    transactionDom = new XElement("Request");
                    transactionDom.Add(new XAttribute("versionId", v));
                    updateRecordsDom = new XElement("Update");
                    updateRecordsDom.Add(new XAttribute("resource", resourceName));
                    insertRecordsDom = new XElement("Insert");
                    insertRecordsDom.Add(new XAttribute("resource", resourceName));
                    transactionDom.Add(updateRecordsDom);
                    transactionDom.Add(insertRecordsDom);

                    // reset counter
                    counter = 0;
                }

                // Step # 8 publish the request
                String publishUrl = baseUrl + "Version.aspx?action=publish&versionId=" + v;
                HttpWebRequest publishRequest = (HttpWebRequest)WebRequest.Create(publishUrl);
                publishRequest.CookieContainer = authCookie;
                publishRequest.Referer = "http://" + baseReferrer + "/index.html";
                WebResponse publishResponse = publishRequest.GetResponse();
                publishResponse.Close();
            }
            catch (Exception ex) 
            {

                // Step # 9 roll back version on error.
                String publishUrl = baseUrl + "Version.aspx?action=publish&versionId=" + v;
                HttpWebRequest publishRequest = (HttpWebRequest)WebRequest.Create(publishUrl);
                publishRequest.CookieContainer = authCookie;
                publishRequest.Referer = "http://" + baseReferrer + "/index.html";
                WebResponse publishResponse = publishRequest.GetResponse();
                publishResponse.Close();
            }

        }

        /// <summary>
        /// Used for this sample to generate data to load. In a real example this is the portion where data is extracted from your
        /// data store for synchronization to a layer within your DMP account.
        /// </summary>
        /// <returns></returns>
        DataTable GetSampleDataTable()
        {

            //
            // Here we create a DataTable with four columns.
            //
            DataTable table = new DataTable();
            table.Columns.Add("ID", typeof(int));
            table.Columns.Add("Modified_Date", typeof(DateTime));
            table.Columns.Add("Street", typeof(string));
            table.Columns.Add("City", typeof(string));
            table.Columns.Add("State", typeof(string));
            table.Columns.Add("Zip", typeof(string));
            table.Columns.Add("Project_Name", typeof(string));
            table.Columns.Add("Project_Type", typeof(string));

            //
            // Here we add five DataRows.
            //
            table.Rows.Add(4, DateTime.Now, "25482 Buckwood", "Lake Forest", "CA", "", "First Build", "New");
            table.Rows.Add(2, DateTime.Now, "5531 AMADOR AVE", "Westminster", "CA", "", "Amador Ave Project", "Add On");
            table.Rows.Add(3, DateTime.Now, "13401 AMARILLO DR", "Westminster", "CA", "", "Amarillo Ave Project", "New");
            table.Rows.Add(4, DateTime.Now, "5581 ALFRED AVE", "Westminster", "CA", "", "Alfred Ave Project", "Add On");
            table.Rows.Add(5, DateTime.Now, "14651 ALLEN ST", "Westminster", "CA", "", "Allen Ave Project", "New");

            return table;

        }

        /// <summary>
        /// Used to post a transaction chunk to the database.
        /// </summary>
        /// <param name="baseUrl">base url to the SpatialStream service</param>
        /// <param name="authCookie">Authentication cookie</param>
        /// <param name="baseReferrer">Used for authentication</param>
        /// <param name="transactionDom">Dom that contains the data to post</param>
        public void postTransactionChunk(string baseUrl, CookieContainer authCookie, string baseReferrer, XElement transactionDom)
        {
            // Step #7 commit the request by using a POST command. 
            // Note that we are preparing all of the requests in a single post. If you are synchronizing a lot of data
            // you will need to move this into the loop and do it in 50 record chunks. 
            HttpWebRequest transactionRequest = (HttpWebRequest)WebRequest.Create(baseUrl + "Transaction.aspx");
            transactionRequest.CookieContainer = authCookie;
            transactionRequest.Referer = "http://" + baseReferrer + "/index.html";

            transactionRequest.Method = "POST";
            String postData = "&xml=" + transactionDom.ToString();

            byte[] byteArray = Encoding.UTF8.GetBytes(postData);
            transactionRequest.ContentType = "application/x-www-form-urlencoded";
            transactionRequest.ContentLength = byteArray.Length;

            Stream dataStream = transactionRequest.GetRequestStream();
            dataStream.Write(byteArray, 0, byteArray.Length);
            dataStream.Close();
            WebResponse response = transactionRequest.GetResponse();

            response.Close();
        }

        /// <summary>
        /// Authenticate to your DMP Account
        /// </summary>
        /// <param name="login">User name</param>
        /// <param name="account">Your account name</param>
        /// <param name="group">Group use "Group1" if you are unsure</param>
        /// <param name="baseUrl">Base URL for the SpatialStream service</param>
        /// <param name="baseReferrer">Used for authentication</param>
        /// <returns></returns>
        private CookieContainer Authenticate(string login, string account, string group, string baseUrl, string baseReferrer)
        {
            CookieContainer cookies = new CookieContainer();

            // create request
            string sLoginRequest = baseUrl + "/admin/getSIK.aspx?ACCOUNT=" + account + "&LOGIN=" + login + "&baseReferrer=" + baseReferrer;

            HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(sLoginRequest);
            HttpWebResponse httpResponse = (HttpWebResponse)httpRequest.GetResponse();
            XmlDocument responseXML = new XmlDocument();
            responseXML.Load(httpResponse.GetResponseStream());

            // check for fail
            XmlNodeList resNodes = responseXML.SelectNodes("/Response/Error/@message");
            if (resNodes.Count != 0)
            {
                throw new ApplicationException("Error authenticating to service! Error=" + resNodes[0].InnerText);
            }

            // check for success
            resNodes = responseXML.SelectNodes("/Response/Success/@message");
            String SIK = "";
            if (resNodes.Count == 1)
            {
                SIK = resNodes[0].InnerText;
            }


            // used in request to make sure we are not using the cache
            Random random = new Random();
            string[] parts = SIK.Split('/');
            string url = "http://" + parts[1] + ".spatialstream.com/" + parts[2] + "/InitSession.aspx?sik=" + parts[2] + "/" + parts[3] + "&output=xml&_=" + random.Next(1000);

            httpRequest = (HttpWebRequest)WebRequest.Create(url);
            httpRequest.Method = "GET";
            httpRequest.ContentType = "application/x-www-form-urlencoded";
            httpRequest.CookieContainer = cookies;


            HttpWebResponse webResponse = (HttpWebResponse)httpRequest.GetResponse();
            StreamReader responseStream = new StreamReader(webResponse.GetResponseStream());
            string requestResponse = responseStream.ReadToEnd();

            // Set new cookies
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.LoadXml(requestResponse);
            if (xmlDoc.SelectSingleNode("//Response[@status = 'success']") == null)
            {
                throw new ApplicationException("Error in InitSesssion");
            }

            webResponse.Close();
            responseStream.Close();

            return cookies;
        }
    }
}